#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <cstdlib>
#include <time.h>
#include "../complexity/complexity.h"

#define N_POP 26
#define N_GEN 1000

using namespace std;
using namespace cv;

int randomFill(Mat*, RNG);
int generateRandomChar(Mat*, RNG);
int randomFlip(Mat* src, Mat* dst, int n);
int randRange(int s, int e);
//int combine(Mat* src1, Mat* src2, Mat* dst, int, int);
int combine(Mat* src1, Mat* src2, Mat* dst);
bool noDuplicates(Mat cur, Mat mats[], int n);

int main(int argc, char* argv[]) {
    Mat cur(100, 100, CV_32FC1); // current character

    Mat oldG[N_POP], newG[N_POP]; // old and new generation
    int comp[N_POP]; // complexities of current generation
    float curcomp; // complexity of current character

    int i, j; // counters

    for (i=0; i<N_POP; i++) {
        oldG[i] = Mat(100, 100, CV_32FC1);
        newG[i] = Mat(100, 100, CV_32FC1);
    }

    // initialize complexity to high numbers
    for (i=0;i<N_POP;i++)
        comp[i] = 1000;

    // initialize random number generators
    RNG rng(time(NULL));
    srand(time(NULL));

    // generate initial population
    for (i=0; i<10000; i++) {
        generateRandomChar(&cur, rng); // generate purely random 'character'
        curcomp = complexity(cur); // compute complexity
        // check if this character has lower complexity than any of the currently stored
        // if so, replace it
        for (j=0;j<N_POP;j++) {
            if (curcomp < comp[j]) {
                oldG[j] = cur.clone();
                newG[j] = cur.clone();
                comp[j] = curcomp;
                break;
            }
        }
    }
    
    int g;
    for (g=0; g<25; g++) {
        // reset complexities (fitness)
        for (i=0;i<N_POP;i++)
            comp[i] = 1000;

        // mutate and evolve iteratively
        for (i=0; i<10000; i++) {
            // choose to either mutate or combine
            bool choice = rand() % 2;

            if (randRange(0, 100) > 90) {
                // mutate
                int a = randRange(0, N_POP);

                randomFlip(&oldG[a], &cur, 100);
                curcomp = complexity(oldG[a]);

                for (j=0;j<N_POP;j++) {
                    if (curcomp < comp[j]) {
                        if (noDuplicates(cur, newG, N_POP)) {
                            newG[j] = cur.clone();
                            comp[j] = curcomp;
                            break;
                        }
                    }
                }

            } else {
                // combine
                int a, b;
                a = randRange(0,N_POP);
                b = randRange(0,N_POP);

                while (a == b)
                    b = randRange(0, N_POP);
                //combine(&oldG[a], &oldG[b], &cur, randRange(0, 2), randRange(0, 2));
                combine(&oldG[a], &oldG[b], &cur);//, randRange(0, 2), randRange(0, 2));

                curcomp = complexity(cur);

                for (j=0;j<N_POP;j++) {
                    if (curcomp < comp[j]) {
                        if (noDuplicates(cur, newG, N_POP)) {
                            newG[j] = cur.clone();
                            comp[j] = curcomp;
                            break;
                        }
                    }
                }
            }
        }
        int p;
        for (p=0;p<N_POP;p++)
            oldG[p] = newG[p].clone();
    }

    namedWindow("Display Image", CV_WINDOW_AUTOSIZE);
    for (i=0;i<N_POP;i++) {
        imshow("Display Image", newG[i]);
        waitKey(0);
    }

    return 0;

}

int generateRandomChar(Mat* dst, RNG rng) {
    Mat random = Mat(20, 20, CV_32FC1);
    randomFill(&random, rng);

    resize(random, *dst, Size(0,0), 5, 5, INTER_NEAREST);

    return 0;
}

int randomFill(Mat* dst, RNG rng) {
    rng = RNG(rand());
    rng.fill(*dst, RNG::UNIFORM, 0, 1);

    threshold(*dst, *dst, 0.5, 1, THRESH_BINARY);

    return 0;
}

int randomFlip(Mat* src, Mat* dst, int n) {
    resize(*src, *dst, Size(0,0), 0.2, 0.2, INTER_NEAREST);
    Size s = dst->size();
    int i, x, y;
    for (i = 0; i < n; i++) {
        x = randRange(0, s.width);
        y = randRange(0, s.height);
        if (dst->at<float>(x, y) < 0.5)
            dst->at<float>(randRange(0, s.width), randRange(0, s.height)) = 1;
        else
            dst->at<float>(randRange(0, s.width), randRange(0, s.height)) = 0;
    }
    resize(*dst, *dst, Size(0,0), 5, 5, INTER_NEAREST);
    return 0;
}

int combine(Mat* src1, Mat* src2, Mat* dst) {
    Mat tmp1, tmp2;
    resize(*src1, tmp1, Size(0,0), 0.2, 0.2, INTER_NEAREST);
    resize(*src2, tmp2, Size(0,0), 0.2, 0.2, INTER_NEAREST);
    int n = tmp1.size().width * tmp1.size().height;
    int c = randRange(0, n);

    *dst = tmp1.clone();

    int i;
    for (i=c;i<n;i++)
        dst->at<float>(i) = tmp2.at<float>(i);
    resize(*dst, *dst, Size(0,0), 5, 5, INTER_NEAREST);

    return 0;
}

/*int combine(Mat* src1, Mat* src2, Mat* dst, int opt1, int opt2) {
    Size s = src1->size();
    *dst = src1->clone();

    int i, j;

    for (i=0.5*s.width*opt1; i<0.5*s.width*opt1+0.5*s.width; i++) {
        for (j=0.5*s.height*opt2; j<0.5*s.height*opt2+0.5*s.height; j++) {
            dst->at<float>(i, j) = src2->at<float>(i,j);
        }
    }

    return 0;
}*/

int randRange(int s, int e) {
    return (int)(s + (float)e*rand()/RAND_MAX);
}

bool noDuplicates(Mat cur, Mat mats[], int n) {
    int i;
    for (i=0; i<n; i++) {
        if (cur.size != mats[i].size || cur.type() != mats[i].type()) {
            cout << "DIFFERENT SIZE " << i << endl;
        }
        if (norm(cur, mats[i]) == 0)
            return false;
    }
//    cout << "TRUE" << endl;
    return true;
}

